package com.atguigu.gmall.gateway.filter;

import com.alibaba.fastjson.JSON;
import com.atguigu.gmall.common.constant.RedisConst;
import com.atguigu.gmall.common.result.Result;
import com.atguigu.gmall.common.result.ResultCodeEnum;
import com.atguigu.gmall.gateway.properties.AuthUrlProperties;
import com.atguigu.gmall.user.entity.UserInfo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.HttpCookie;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.concurrent.CompletableFuture;

/**
 * @author lfy
 * @Description 拦截每个请求，透传用户id
 * @create 2022-12-15 14:11
 */
@Slf4j
@Component
public class UserAuthGatewayFilter implements GlobalFilter {


    @Autowired
    AuthUrlProperties urlProperties;


    AntPathMatcher pathMatcher = new AntPathMatcher();

    /**
     * @param exchange 包括了这次的请求和响应
     * @param chain    filter链
     * @return
     */
    @Override
    public Mono<Void> filter(ServerWebExchange exchange,
                             GatewayFilterChain chain) {
        String path = exchange.getRequest().getURI().getPath();
        log.info("请求开始：{}", path);

        //【1：静态资源放行】
        //如果是静态资源，直接放行无需查询用户身份信息 如：/img/cartPanelViewIcons.png
        long count = urlProperties.getAnyoneUrl()
                .stream()
                .filter(pattern -> pathMatcher.match(pattern, path))//当前：/css/all.css    是否匹配：/css/**
                .count();
        if (count > 0) {
            log.info("静态资源直接放行....");
            return chain.filter(exchange);
        }


        //【2：直接拒绝访问的路径】
        long denyCount = urlProperties.getDenyUrl()
                .stream()
                .filter(pattern -> pathMatcher.match(pattern, path))
                .count();
        if (denyCount > 0) {
            log.warn("浏览器请求内部路径. 疑似攻击，直接打回");
            Result<String> result = Result.build("", ResultCodeEnum.PERMISSION);
            //返回错误json
            return responseJson(exchange, result);
        }


        //3、【有限权限访问】  必须登录才访问
        long authCount = urlProperties.getAuthUrl()
                .stream()
                .filter(pattern -> pathMatcher.match(pattern, path))
                .count();
        if (authCount > 0) {
            //必须登录才能访问； 能拿到令牌说明登录了，否则没登录
            String token = getToken(exchange);
            UserInfo userInfo = getUserInfo(token);
            if (StringUtils.isEmpty(token) || userInfo == null) {
                //浏览器重定向到登录页去登录
                return redirectToPage(exchange, urlProperties.getLoginPage());
            }
        }

        log.info("验证用户信息开始....透传用户id");
        //所有请求都是正常情况下的统一功能


        //1、获取请求头或者cookie中携带的用户令牌标识
        //2、根据请求带来的token，获取redis中登录的用户信息
        UserInfo userInfo = getUserInfo(getToken(exchange));
        //如果能拿到用户的信息。我们就透传id

        //透传id
        return userIdThrougth(chain,exchange,userInfo);


        //filter放行
//        Mono<Void> filter = chain
//                .filter(exchange)
//                .doFinally((item) -> {
////                    log.info("请求结束：{}",request.getURI().toString());
//                }); //这个方法是非阻塞的
//        return filter;
    }

    private Mono<Void> userIdThrougth(GatewayFilterChain chain, ServerWebExchange exchange, UserInfo userInfo) {
        //1、透传。 请求的数据都是只读的不能修改
        ServerHttpRequest.Builder reqBuilder = exchange.getRequest()
                .mutate(); //说清楚要变这个东西

        //2、用户id放在请求头
        if(userInfo!=null){
            log.info("用户登录信息放在头中，往下透传");
            //只要构造完成，老的exchange里面的request也会跟着变
            reqBuilder.header(RedisConst.USER_ID_HEADER, userInfo.getId().toString())
                    .build();
        }

        //3、临时id
        String tempId = getTempId(exchange);
        if(!StringUtils.isEmpty(tempId)){
            log.info("用户临时信息放在头中，往下透传");
            //只要构造完成，老的exchange里面的request也会跟着变
            reqBuilder.header(RedisConst.TEMP_ID_HEADER, tempId)
                    .build();
        }

        //4、放行
        return chain.filter(exchange);
    }

    private String getTempId(ServerWebExchange exchange) {
        //1、拿到请求
        ServerHttpRequest request = exchange.getRequest();
        //2、先看请求头
        String token = request.getHeaders().getFirst("userTempId");
        if (!StringUtils.isEmpty(token)) {
            return token;
        }

        //3、如果头没有，就看cookie； 有可能没登录这个cookie都没有
        HttpCookie first = request.getCookies().getFirst("userTempId");
        if (first != null) {
            return first.getValue();
        }
        return null;
    }

    /**
     * 重定向到页面
     *
     * @param exchange
     * @param loginPage
     * @return
     */
    private Mono<Void> redirectToPage(ServerWebExchange exchange, String loginPage) {
        ServerHttpResponse response = exchange.getResponse();
        //当时的请求路径
        URI uri = exchange.getRequest().getURI();
        //设置好要跳转的地址
        loginPage += "?originUrl=" + uri.toString();

        //1、设置响应状态码  302
        response.setStatusCode(HttpStatus.FOUND);

        //2、设置响应头  Location: 新位置  http://passport.gmall.com/login.html
        response.getHeaders().set("Location", loginPage);
        return response.setComplete();
    }


    /**
     * 响应json
     *
     * @param exchange
     * @param result
     * @return
     */
    private Mono<Void> responseJson(ServerWebExchange exchange, Result result) {

        //1、得到响应对象
        ServerHttpResponse response = exchange.getResponse();

        //2、得到数据的DataBuffer
        String json = JSON.toJSONString(result);
        DataBuffer dataBuffer = response
                .bufferFactory()
                .wrap(json.getBytes(StandardCharsets.UTF_8));
        response.getHeaders().setContentType(MediaType.APPLICATION_JSON_UTF8);

        return response.writeWith(Mono.just(dataBuffer));
    }


    @Autowired
    StringRedisTemplate redisTemplate;

    /**
     * 根据请求带来的token，获取redis中登录的用户信息
     *
     * @param token
     * @return
     */
    private UserInfo getUserInfo(String token) {
        if(StringUtils.isEmpty(token)){
            return null;
        }
        String json = redisTemplate.opsForValue().get("login:user:" + token);
        if (StringUtils.isEmpty(json)) {
            return null;
        }
        UserInfo userInfo = JSON.parseObject(json, UserInfo.class);
        return userInfo;
    }


    /**
     * 获取请求头或者cookie中携带的用户令牌标识
     *
     * @param exchange
     * @return
     */
    private String getToken(ServerWebExchange exchange) {
        //1、拿到请求
        ServerHttpRequest request = exchange.getRequest();
        //2、先看请求头
        String token = request.getHeaders().getFirst("token");
        if (!StringUtils.isEmpty(token)) {
            return token;
        }

        //3、如果头没有，就看cookie； 有可能没登录这个cookie都没有
        HttpCookie first = request.getCookies().getFirst("token");
        if (first != null) {
            return first.getValue();
        }
        return null;
    }


}
